﻿#include "chatlistmodel.h"
#include <QAbstractItemView>
#include <QApplication>
#include <QCryptographicHash>
#include <QDateTime>
#include <QImageReader>
#include <QLayout>
#include <QPainter>
#include <QPixmapCache>
#include <QStringBuilder>
#include <QtMath>
#include "characterset.h"
#include "chatitemwidget.h"
#include "httpget.h"

ChatListModel::ChatListModel(QObject *parent) : QAbstractListModel(parent), lastTimeStamp(0)
{
}

int ChatListModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return contents.size();
}

QVariant ChatListModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    if(row < 0 || row >= contents.size())
        return QVariant();
    return contents.at(row)->data(role);
}

Qt::ItemFlags ChatListModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags f = QAbstractItemModel::flags(index);
    if (f > 0)
    {
        f = Qt::ItemNeverHasChildren;
    }
    return f;
}

int ChatListModel::appendContent(ChatContent *content, qint64 timestamp)
{
    ChatPlainText *timeString = Q_NULLPTR;
    auto currentDateTime = QDateTime::currentDateTime();
    auto tsDatetime = timestamp ? QDateTime::fromMSecsSinceEpoch(timestamp) : currentDateTime;
    auto lastDateTime = QDateTime::fromMSecsSinceEpoch(lastTimeStamp);
    qint64 daysTo = tsDatetime.daysTo(currentDateTime);
    if(daysTo)
    {
        if(tsDatetime.daysTo(lastDateTime))
        {
            //要显示日期
            if(daysTo == 1)
                timeString = new ChatPlainText(QObject::trECSUTF8("昨天"));
            else if (lastDateTime.date().year() == tsDatetime.date().year() || currentDateTime.date().year() == tsDatetime.date().year()) {
                //和上一条同年或者和现在同年: 只显示月日
                timeString = new ChatPlainText(tsDatetime.toString(QLatin1String("MM-dd")));
            }
            else {
                //显示年月日
                timeString = new ChatPlainText(tsDatetime.toString(QLatin1String("yyyy-MM-dd")));
            }
        }
    }
    else if(lastDateTime.secsTo(tsDatetime) > 5 * 60)
    {
        //今天，要显示时间
        if(lastDateTime.daysTo(tsDatetime))
            timeString = new ChatPlainText(tsDatetime.toString(QObject::trECSUTF8("今天")+ QLatin1String(" hh:mm")));
        else
            timeString = new ChatPlainText(tsDatetime.toString(QLatin1String("hh:mm")));
    }
    lastTimeStamp = timestamp;
    int start = contents.size();
    int end = timeString ? start + 1 : start;
    beginInsertRows(QModelIndex(), start, end);
    if(timeString)
        contents.append(QSharedPointer<ChatContent>(timeString));
    contents.append(QSharedPointer<ChatContent>(content));
    endInsertRows();
    return end;
}

ChatContent *ChatListModel::getContent(int row) const
{
    if(row < 0 || row >= contents.size())
        return Q_NULLPTR;
    return contents.at(row).data();
}

QPair<int, ChatDirection *> ChatListModel::findDirection(const QByteArray &mid) const
{
    QPair<int, ChatDirection *> result(-1, Q_NULLPTR);
    if(mid.isEmpty())
        return result;
    int index = 0;
    for(const auto &content : contents)
    {
        const auto dir = dynamic_cast<ChatDirection*>(content.data());
        if(dir && dir->mid == mid)
        {
            result.first = index;
            result.second = dir;
            return result;
        }
        index++;
    }
    return result;
}

QPair<int, ChatImage *> ChatListModel::findImage(const QString &url) const
{
    QPair<int, ChatImage *> result(-1, Q_NULLPTR);
    int index = 0;
    for(const auto &content : contents)
    {
        if(auto image = dynamic_cast<ChatImage*>(content.data()))
        {
            if(image->getUrl() == url)
            {
                result.first = index;
                result.second = image;
                return result;
            }
        }
        index++;
    }
    return  result;
}

void ChatListModel::downloadRequested(QString url)
{
    if(waitingList.contains(url))
        return;
    waitingList.append(url);
    auto get = new HttpGet(url, false, url);
    connect(get, SIGNAL(finished(QByteArray,QVariant)), SLOT(downloadFinished(QByteArray,QVariant)));
    connect(get, &HttpGet::error, this, &ChatListModel::downloadError);
    connect(get, &HttpGet::downloadProgress, this, &ChatListModel::downloadProgress);
}

void ChatListModel::clearAll()
{
    beginResetModel();
    contents.clear();
    lastTimeStamp = 0;
    waitingList.clear();
    endResetModel();
}

static inline QString keyForUrl(const QString &url)
{
    return QLatin1String("url_pixmap_") % url;
}

void ChatListModel::downloadFinished(QByteArray buffer, QVariant userData)
{
    auto url = userData.toString();
    if(url.isEmpty())
        return;
    if(!waitingList.removeOne(url))
        return;

    const char * format = Q_NULLPTR;
    QByteArray suffix = QUrl(url).fileName().section('.', -1).toLatin1().toLower();
    if(!suffix.isEmpty() && QImageReader::supportedImageFormats().contains(suffix))
        format = suffix;

    QPixmap pixmap;
    pixmap.loadFromData(buffer, format);
    if(pixmap.isNull())
        pixmap.loadFromData(buffer);

    QPixmapCache::insert(keyForUrl(url), pixmap);

    auto result = findImage(url);
    if(result.second)
    {
        result.second->downloadFinished(pixmap, QCryptographicHash::hash(buffer, QCryptographicHash::Md5).toHex());
        auto index = this->index(result.first);
        emit dataChanged(index, index);
    }
}

void ChatListModel::downloadError(QVariant userData)
{
    auto url = userData.toString();
    if(url.isEmpty())
        return;
    if(!waitingList.removeOne(url))
        return;

    auto result = findImage(url);
    if(result.second)
    {
        result.second->downloadError();
        auto index = this->index(result.first);
        emit dataChanged(index, index);
    }
}

void ChatListModel::downloadProgress(qint64 received, qint64 total, QVariant userData)
{
    auto url = userData.toString();
    if(url.isEmpty())
        return;
    auto result = findImage(url);
    if(result.second)
    {
        result.second->downloadProgress(received * 100 / total / 10);
        auto index = this->index(result.first);
        emit dataChanged(index, index);
    }
}

ChatListDelegate::ChatListDelegate(QObject *parent) : QItemDelegate(parent)
{
}

QSize ChatListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QSize sh = QItemDelegate::sizeHint(option, index);
    if(auto view = qobject_cast<const QAbstractItemView*>(option.widget))
    {
        if(auto widget = view->indexWidget(index))
        {
            if(widget->hasHeightForWidth())
            {
                int width = option.rect.width();
                if(width <= 0 && view->viewport())
                    width = view->viewport()->width();
                if(width > 0)
                {
                    int height = widget->heightForWidth(width);
                    if(height > sh.height())
                        return QSize(width, height).expandedTo(sh);
                }
            }
            else
                return widget->sizeHint().expandedTo(sh);
        }
    }
    return sh;
}

QWidget *ChatListDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/*option*/, const QModelIndex &index) const
{
    if (!index.isValid())
        return Q_NULLPTR;
    return new ChatItemWidget(index.data(Qt::UserRole).toBool(), index.data(Qt::DisplayRole),
                              index.data(Qt::DecorationRole).value<QPixmap>(), index.data(Qt::UserRole + 1).toString(), parent);
}

void ChatListDelegate::destroyEditor(QWidget *editor, const QModelIndex &/*index*/) const
{
    if(editor)
        editor->deleteLater();
}

void ChatListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    if(auto itemWidget = qobject_cast<ChatItemWidget*>(editor))
        itemWidget->setDisplay(index.data(Qt::DisplayRole), index.data(Qt::EditRole), index.data(Qt::UserRole + 1).toString());
}

void ChatListDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(editor)
        editor->setGeometry(option.rect);
}

ChatContent *ChatListDelegate::getContent(const QModelIndex &index) const
{
    if(auto model = qobject_cast<const ChatListModel*>(index.model()))
        return model->getContent(index.row());
    return Q_NULLPTR;
}
